package org.management.utils;

import org.docx4j.TextUtils;
import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.ClassFinder;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import org.springframework.util.ObjectUtils;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import java.io.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 功能描述
 *
 * @author jy
 * @date 2019/5/5
 * @description
 * @project management
 */
public class WordTemplate {

    private WordprocessingMLPackage wordMLPackage = null;

    private ObjectFactory factory = null;

    /**
     * 根据路径加载word模板
     * @param path 相对路径
     */
    public void loadWordTemplate(String path) {
        InputStream is = WordTemplate.class.getClassLoader().getResourceAsStream(path);
        loadWordTemplate(is);
    }

    /**
     * 根据文件加载word模板
     * @param file 真实文件
     */
    public void loadWordTemplate(File file) {
        InputStream is;
        try {
            is = new FileInputStream(file);
            loadWordTemplate(is);
        } catch (FileNotFoundException e) {
            LoggerUtils.error(WordTemplate.class, "word模板不存在");
            e.printStackTrace();
        }
    }

    /**
     * 根据流加载word模板
     * @param is 流
     */
    public void loadWordTemplate(InputStream is) {
        try {
            if (is == null) throw new FileNotFoundException("模板文件不存在");
            factory = Context.getWmlObjectFactory();
            wordMLPackage = WordprocessingMLPackage.load(is);
        } catch (Docx4JException e) {
            LoggerUtils.error(WordTemplate.class, "创建word失败");
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            LoggerUtils.error(WordTemplate.class, "模板文件不存在");
            e.printStackTrace();
        } catch (Exception e) {
            LoggerUtils.error(WordTemplate.class, "可能存在字体不存在");
            e.printStackTrace();
        }
    }


    /**
     * (复制粘贴 可能导致${name} 表达式与内容分离 导致无法替换)
     * @param variables 文本键值对 (文本、图片)（文本 支持 String 类型  图片 支持 byte[] Inputstream 类型）
     */
    public void replaceDocument(Map<String, Object> variables) {
        replaceText(variables, null, null);
    }


    /**
     * replaceText 必须最后执行 否则表达式全部替换其他方法不会生效
     * (复制粘贴 可能导致${name} 表达式与内容分离 导致无法替换)
     * @param variables 文本键值对 (文本、图片)（文本 支持 String 类型  图片 支持 byte[] Inputstream 类型 list 表格）
     * @param mergeMap 纵向合并（相邻相同内容合并）
     */
    public void replaceDocument(Map<String, Object> variables, Map<String, int []> mergeMap) {
        replaceText(variables, mergeMap, false);
    }

    /**
     * replaceText 必须最后执行 否则表达式全部替换其他方法不会生效
     * (复制粘贴 可能导致${name} 表达式与内容分离 导致无法替换)
     * @param variables 文本键值对 (文本、图片)（文本 支持 String类型 图片支持 byte[] Inputstream 类型 list(Map、Entity)表格）
     * @param total 是否横向合并（显示第一个单元格内容）
     *              设置为true 同时 数据集 中每一列需要设置 startNum 开始合并下标 endNum 结束合并下标(示例见 main方法)
     */
    public void replaceDocument(Map<String, Object> variables, Boolean total) {
        replaceText(variables, null, total);
    }

    /**
     * replaceText 必须最后执行 否则表达式全部替换其他方法不会生效
     * (复制粘贴 可能导致${name} 表达式与内容分离 导致无法替换)
     * @param variables 文本键值对 (文本、图片)（文本 支持 String类型 图片支持 byte[] Inputstream 类型 list(Map、Entity)表格）
     * @param mergeMap 纵向合并（相邻相同内容合并）
     * @param total 是否横向合并（显示第一个单元格内容）
     *              设置为true 同时 数据集 中每一列需要设置 startNum 开始合并下标 endNum 结束合并下标(示例见 main方法)
     */
    public void replaceDocument(Map<String, Object> variables, Map<String, int []> mergeMap, Boolean total) {
        replaceText(variables, mergeMap, total);
    }
    /**
     * 替换文本 (复制粘贴 可能导致${name} 表达式与内容分离 导致无法替换)
     *
     * @param variables key-value 例(a-文本)
     */
    private void replaceText(Map<String, Object> variables, Map<String, int []> mergeMap, Boolean total) {
        try {
            List<Object> objects = getAllElementFromObject(Text.class);
            for (Object object : objects) {
                Text text = (Text) object;
                String key = matchValue(text.getValue());
                if (key == null) continue;
                if (variables.containsKey(key)) {
                    Object value = variables.get(key);
                    if (value instanceof List) {
                        int [] mergeRow = null;
                        if (mergeMap != null)  mergeRow = mergeMap.get(key);
                        setContent(text, (List)value, mergeRow, total);
                    } else {
                        setContent(text, value);
                    }
                } else {
                    text.setValue("");
                }
            }
        } catch (Exception e) {
            LoggerUtils.error(WordTemplate.class, "替换文本失败");
            e.printStackTrace();
        }
    }

    private void setContent(Text text, List<?> list, int [] mergeRow, Boolean total) {
        Tr dataTr = (Tr)((Tc)((P)((R)text.getParent()).getParent()).getParent()).getParent();
        Tbl tbl = (Tbl)dataTr.getParent();
        List<Tr> trs = getTblAllTr(tbl);
        int index = trs.indexOf(dataTr);
        int trsNum = index;
        tbl.getContent().remove(index);
        Tr tr = (Tr)tbl.getContent().get(index);
        String trXml = XmlUtils.marshaltoString(tr);
        tbl.getContent().remove(index);
        List<Map<String,Object>> dataList = new ArrayList<>();
        for (Object object : list) {
            Map<String,Object> map;
            if (!(object instanceof Map)) {
                map = MapObjUtil.objectConvertMap(object);
            } else {
                map = (Map<String,Object>)object;
            }
            dataList.add(map);
        }
        try {
            for (Map<String, Object> dataMap : dataList) {
                Tr newTr = (Tr)XmlUtils.unmarshallFromTemplate(trXml, dataMap);
                tbl.getContent().add(index++, newTr);
                if (total) {
                    if (!ObjectUtils.isEmpty(dataMap.get("startNum")) && !ObjectUtils.isEmpty(dataMap.get("endNum"))) {
                        Integer startNum = Integer.parseInt(dataMap.get("startNum").toString());
                        Integer endNum = Integer.parseInt(dataMap.get("endNum").toString());
                        mergeCellsHorizontalByGridSpan(tbl, index - 1, startNum, endNum);
                    }
                }
            }
            if (mergeRow != null) {
                mergeTable(tbl, trsNum, mergeRow);
            }
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }


    private void setContent(Text text, Object object) throws IOException {
        if (object instanceof byte []) {
            byte[] bytes = (byte[])object;
            replaceImages(text, bytes);
        } else if (object instanceof InputStream) {
            InputStream is = (InputStream)object;
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024*4];
            int n;
            while (-1 != (n = is.read(buffer))) {
                output.write(buffer, 0, n);
            }
            byte[] bytes = output.toByteArray();
            replaceImages(text, bytes);
            output.close();
        } else {
            if (StringUtils.isEmpty(object)) text.setValue("");
            text.setValue(object.toString());
        }
    }

    /**
     * 查询word 中class类的集合信息
     * @param clazz 类 例(Text.class)
     * @return 对象集合
     */
    private List getAllElementFromObject(Class clazz){
        ClassFinder finder = new ClassFinder(clazz);
        MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
        new TraversalUtil(documentPart.getContent(), finder);
        return finder.results;
    }

    private String matchValue(String value) {
        String regex = "\\$\\{(\\w*|\\W*|[\\u4e00-\\u9fa5])\\}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(value);
        if (matcher.find()) {
            value = matcher.group(1);
            return value;
        }
        return null;
    }

    /**
     * 替换图片键值对
     * @param text 替换文本位置
     * @param bytes 图片字节码
     */
    private void replaceImages(Text text, byte[] bytes) {
        try {
            BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
            Inline inline = imagePart.createImageInline(null, null, 0, 1, false, 0);
            Drawing drawing = factory.createDrawing();
            drawing.getAnchorOrInline().add(inline);
            R run = (R)text.getParent();
            run.getContent().clear();
            run.getContent().add(drawing);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public void mergeCellsHorizontalByGridSpan(Tbl tbl, int row, int fromCell, int toCell) {
        if (row < 0 || fromCell < 0 || toCell < 0) {
            return;
        }
        List<Tr> trList = getTblAllTr(tbl);
        if (row > trList.size()) {
            return;
        }
        Tr tr = trList.get(row);
        List<Tc> tcList = getTrAllCell(tr);
        for (int cellIndex = Math.min(tcList.size() - 1, toCell); cellIndex >= fromCell; cellIndex--) {
            Tc tc = tcList.get(cellIndex);
            TcPr tcPr = getTcPr(tc);
            if (cellIndex == fromCell) {
                TcPrInner.GridSpan gridSpan = tcPr.getGridSpan();
                if (gridSpan == null) {
                    gridSpan = new TcPrInner.GridSpan();
                    tcPr.setGridSpan(gridSpan);
                }
                gridSpan.setVal(BigInteger.valueOf(Math.min(tcList.size() - 1, toCell) - fromCell + 1));
            } else {
                tr.getContent().remove(cellIndex);
            }
        }
    }

    /**
     * @param tbl       表格信息
     * @param rowNum    开始行下标 (跳过标题)
     * @param verticalRow 合并列
     */
    private void mergeTable(Tbl tbl, int rowNum, int[] verticalRow) {
        List trs = getTblAllTr(tbl);
        int size = trs.size();
        Map<String, Object> rowMap = new HashMap<>();
        for (int row = rowNum; row < size; row++) {
            boolean isSpan = false;
            for (int index : verticalRow) {
                int spanIndex = 0;
                if (index > 0) {
                    int lastIndex = index;
                    while (lastIndex > 0) {
                       Tc oldTc = getTc(tbl, row,  --lastIndex);
                       if(oldTc == null) continue;
                       TcPr tcPr = oldTc.getTcPr();
                       if (tcPr != null) {
                           TcPrInner.GridSpan span = tcPr.getGridSpan();
                           if (span != null) {
                               int value = span.getVal().intValue();
                               spanIndex += value - 1;
                               if (index - spanIndex <= 0) {
                                   isSpan = true;
                               }
                           }
                       }
                    }
                }
                if (isSpan) break;
                Tc tc = getTc(tbl, row, index - spanIndex);
                String newValue = index + getElementContent(tc);
                if (!rowMap.containsKey(newValue)) {
                    setVMerge(tc, true);
                    rowMap.put(newValue, null);
                } else {
                    if (rowMap.containsKey(newValue)) {
                        setVMerge(tc, false);
                        rowMap.put(newValue, null);
                    } else {
                        setVMerge(tc, true);
                    }
                }
            }
            if (isSpan) break;
        }
    }

    /**
     * @Description: 设置合并
     */
    private void setVMerge(Tc tc, Boolean isStart) {
        if (tc != null) {
            TcPr tcPr = getTcPr(tc);
            TcPrInner.VMerge vMerge = tcPr.getVMerge();
            if (vMerge == null) {
                vMerge = new TcPrInner.VMerge();
                tcPr.setVMerge(vMerge);
                CTVerticalJc vjc = new CTVerticalJc();
                vjc.setVal(STVerticalJc.CENTER);
                tcPr.setVAlign(vjc);
            }
            if (isStart) {
                vMerge.setVal("restart");
            } else {
                vMerge.setVal("continue");
            }
        }
    }

    /**
     * 获取对象内容 例（单元格）
     *
     * @param obj 对象
     * @return
     * @throws Exception
     */
    private String getElementContent(Object obj) {
        if (obj == null) {
            return "";
        }
        StringWriter stringWriter = new StringWriter();
        try {
            TextUtils.extractText(obj, stringWriter);
        } catch (Exception e) {
            LoggerUtils.error("对象转换文本失败");
            e.printStackTrace();
        }
        return stringWriter.toString();
    }

    /**
     * @Description: 得到表格所有的行
     */
    private List<Tr> getTblAllTr(Tbl tbl) {
        List<Object> objList = tbl.getContent();
        List<Tr> trList = new ArrayList<>();
        if (objList == null) {
            return trList;
        }
        for (Object obj : objList) {
            if (obj instanceof Tr) {
                Tr tr = (Tr) obj;
                trList.add(tr);
            }
        }
        return trList;

    }

    /**
     * @Description: 得到表格所有的行
     */
    private Tr getTr(Tbl tbl, int rowIndex) {
        if (rowIndex < 0) {
            return null;
        }
        List<Object> objList = tbl.getContent();
        if (objList == null) {
            return null;
        }
        if (rowIndex >= objList.size()) {
            return null;
        }
        return (Tr) objList.get(rowIndex);

    }

    /**
     * @Description 获取所有的单元格
     */
    private List<Tc> getTrAllCell(Tr tr) {
        List<Object> objList = tr.getContent();
        List<Tc> tcList = new ArrayList<>();
        if (objList == null) {
            return tcList;
        }
        for (Object tcObj : objList) {
            JAXBElement element = (JAXBElement) tcObj;
            Tc tc = (Tc)element.getValue();
            tcList.add(tc);
        }
        return tcList;
    }

    /**
     * @Description 得到指定位置的单元格
     */
    private Tc getTc(Tbl tbl, int rowIndex, int cellIndex) {
        if (cellIndex < 0) {
            return null;
        }
        Tr tr = getTr(tbl, rowIndex);
        List<Tc> objList = getTrAllCell(tr);
        if (objList == null) {
            return null;
        }
        if (cellIndex >= objList.size()) {
            return null;
        }
        Tc tc = objList.get(cellIndex);
        return tc;
    }

    private TcPr getTcPr(Tc tc) {
        if (tc == null) {
            return null;
        }
        TcPr tcPr = tc.getTcPr();
        if (tcPr == null) {
            tcPr = new TcPr();
            tc.setTcPr(tcPr);
        }
        return tcPr;
    }

    public void write(OutputStream os) {
        try {
            wordMLPackage.save(os);
        } catch (Docx4JException e) {
            e.printStackTrace();
        }
    }

    public void write(File file) {
        try {
            wordMLPackage.save(file);
        } catch (Docx4JException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        WordTemplate template = new WordTemplate();
        template.loadWordTemplate("demo.docx");
        List<List<Map<String, Object>>> lists = new ArrayList<>();
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, int[]> mergeMap = new HashMap();
        mergeMap.put("list" ,new int[] {4});
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("name", "小米");
        Map<String, Object> dataMap1 = new HashMap<>();
        dataMap1.put("name", "小米1");
        dataMap1.put("startNum", 0);
        dataMap1.put("endNum", 2);
        list.add(dataMap);
        list.add(dataMap1);
        Map<String, Object> variables = new HashMap<>();
        variables.put("title", "箭在弦上");
        variables.put("list", list);
        lists.add(list);
        template.replaceDocument(variables, mergeMap, true);
        File file = new File("D://dd.docx");
        template.write(file);
    }


}
